package com.hsu.usercenter.service.impl;

import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.google.gson.Gson;
import com.google.gson.reflect.TypeToken;
import com.hsu.usercenter.common.ErrorCode;
import com.hsu.usercenter.constant.UserConstant;
import com.hsu.usercenter.exception.BusinessException;
import com.hsu.usercenter.mapper.UserMapper;
import com.hsu.usercenter.model.domain.request.UserCreateRequest;
import com.hsu.usercenter.service.UserService;
import com.hsu.usercenter.model.domain.User;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
//import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
//import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;
import org.springframework.util.DigestUtils;
import org.springframework.web.multipart.MultipartFile;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.StandardCopyOption;
import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;

import static com.hsu.usercenter.constant.UserConstant.ADMIN_ROLE;
import static com.hsu.usercenter.constant.UserConstant.USER_LOGIN_STATE;

/**
 * 用户服务实现类
 * @author 幽琴健
 * @description 针对表【user(用户)】的数据库操作Service实现
 * @createDate 2024-03-10 15:40:12
 */
@Service
@Slf4j
public class UserServiceImpl extends ServiceImpl<UserMapper, User>
        implements UserService {
    @Resource
    private UserMapper userMapper;

    @Resource
    private BCryptPasswordEncoder passwordEncoder;

    //盐值，用于密码加密，增加密码复杂度
    private static final String SALT = "yupi";

    /**
     * 根据标签搜索用户（SQL查询）
     * @param tagNameList 用户输入的标签列表
     * @return 符合条件的用户列表
     * @throws BusinessException 当 `tagNameList` 为空或 `null` 时抛出参数错误异常
     */
    @Deprecated
    private List<User> searchUsersByTagsSql(List<String> tagNameList) {
        long start = System.currentTimeMillis();
        // 检查标签列表是否为空
        if (CollectionUtils.isEmpty(tagNameList)) {
            throw new BusinessException(ErrorCode.PARAMS_ERROR);
        }
        // 初始化查询包装器，并拼接标签的 AND 查询条件
        QueryWrapper<User> queryWrapper = new QueryWrapper<>();
        for (String tagName : tagNameList) {
            queryWrapper.like("tags", tagName);
        }
        // 从数据库中根据查询条件检索用户列表
        List<User> userList = userMapper.selectList(queryWrapper);
        long end = System.currentTimeMillis();
        log.info("SQL查询耗时：" + (end - start));
        // 返回安全用户对象列表
        return userList.stream().map(this::getSafetyUser).collect(Collectors.toList());
    }

    /**
     * 根据标签搜索用户（内存过滤）
     * @param tagNameList 用户输入的标签列表
     * @return 符合条件的用户列表
     * @throws BusinessException 当 `tagNameList` 为空或 `null` 时抛出参数错误异常
     */
    private List<User> searchUsersByTagsInMemory(List<String> tagNameList) {
        long start = System.currentTimeMillis();
        // 检查标签列表是否为空
        if (CollectionUtils.isEmpty(tagNameList)) {
            throw new BusinessException(ErrorCode.PARAMS_ERROR);
        }
        // 查询所有用户
        QueryWrapper<User> queryWrapper = new QueryWrapper<>();
        List<User> userList = userMapper.selectList(queryWrapper);
        Gson gson = new Gson();
        // 在内存中过滤不符合标签要求的用户
        List<User> filteredList = userList.stream()
                .filter(user -> {
                    String tagsStr = user.getTags();
                    // 如果标签字段为空，则过滤掉该用户
                    if (StringUtils.isBlank(tagsStr)) {
                        return false;
                    }
                    // 将 JSON 字符串解析为标签集合
                    Set<String> tmpTagNameSet = gson.fromJson(tagsStr, new TypeToken<Set<String>>() {}.getType());
                    // Java8特性：利用 Optional 判空，确保 tmpTagNameSet 不是空值，避免了空指针异常
                    tmpTagNameSet= Optional.ofNullable(tmpTagNameSet).orElse(new HashSet<>());
                    // 如果用户的标签集合不包含所有目标标签，则过滤掉该用户
                    for (String tagName : tagNameList) {
                        if (!tmpTagNameSet.contains(tagName)) {
                            return false;
                        }
                    }
                    return true;
                })
                // 将用户对象转换为安全用户对象
                .map(this::getSafetyUser)
                // 收集符合条件的用户列表
                .collect(Collectors.toList());
        long end = System.currentTimeMillis();
        log.info("内存查询耗时：" + (end - start));
        return filteredList;
    }

    /**
     * 判断账户是否已经存在
     * @param userAccount
     * @return
     */
    private boolean checkUserAccountExists(String userAccount) {
        QueryWrapper<User> queryWrapper = new QueryWrapper<>();
        queryWrapper.eq("userAccount", userAccount);
        return count(queryWrapper) > 0;
    }

    /**
     * 判断星球编号是否已经存在
     * @param planetCode
     * @return
     */
    private boolean checkPlanetCodeExists(String planetCode) {
        QueryWrapper<User> queryWrapper = new QueryWrapper<>();
        queryWrapper.eq("planetCode", planetCode);
        return count(queryWrapper) > 0;
    }

    /**
     * 用户注册方法。
     * 本方法负责处理新用户的注册逻辑，包括校验输入参数的合法性、确保用户名和星球编号的唯一性、
     * 密码加密和用户信息保存到数据库。
     * @param userAccount 用户账户名，需保证唯一性，且不能包含特殊字符，长度不少于4位。
     * @param userPassword 用户设置的密码，与checkPassword比较后一致才能继续，长度不少于8位。
     * @param checkPassword 用户输入的密码确认，需与userPassword一致。
     * @param planetCode 用户的星球编号，需保证唯一性，长度不超过5位。
     * @return 成功注册的用户ID。如果注册过程中遇到任何问题（如参数校验失败、用户名或星球编号已存在等），将抛出BusinessException。
     *         如果账户名包含特殊字符、密码和确认密码不一致时，直接返回-1。
     * @throws BusinessException 如果输入参数不满足要求或者用户名、星球编号已被使用，将抛出此异常，异常信息中包含具体的错误原因。
     */
    @Override
    public long userRegister(String userAccount, String userPassword, String checkPassword, String planetCode) {
        // 1.校验
        // 检查注册信息是否完整。若任一字段为空，则抛出参数错误的业务异常。
        if (StringUtils.isAnyBlank(userAccount, userPassword, checkPassword, planetCode)) {
            throw new BusinessException(ErrorCode.NULL_ERROR, "注册信息不完整，请填写所有字段。");
        }
        // 检查账号长度是否符合要求，账号长度不得少于4位。
        if (userAccount.length() < 4) {
            throw new BusinessException(ErrorCode.PARAMS_ERROR, "用户账号长度过短，至少需要4位。");
        }
        // 检查密码长度是否符合要求，密码长度不得少于8位。
        if (userPassword.length() < 8 || checkPassword.length() < 8) {
            throw new BusinessException(ErrorCode.PARAMS_ERROR, "密码长度过短，至少需要8位。");
        }
        // 检查星球编号长度，不得超过5位。
        if (planetCode.length() > 5) {
            throw new BusinessException(ErrorCode.PARAMS_ERROR, "星球编号过长，不得超过5位。");
        }
        // 检查账号中是否包含特殊字符，若包含，则不允许注册。
        String validPattern = "[`~!@#$%^&*()+=|{}':;',\\\\[\\\\].<>/?~！@#￥%……&*（）——+|{}【】‘；：”“’。，、？]";
        Matcher matcher = Pattern.compile(validPattern).matcher(userAccount);
        if (matcher.find()) {
            return -1; // 直接返回错误码，表示账号包含非法字符。
        }
        // 检查密码与确认密码是否一致。
        if (!userPassword.equals(checkPassword)) {
            return -1; // 直接返回错误码，表示密码不一致。
        }
        // 查询数据库，检查账号是否已被注册。
        if (checkUserAccountExists(userAccount)) {
            throw new BusinessException(ErrorCode.PARAMS_ERROR, "该账号已被注册。");
        }
        // 查询数据库，检查星球编号是否已被使用。
        if (checkPlanetCodeExists(planetCode)) {
            throw new BusinessException(ErrorCode.PARAMS_ERROR, "星球编号已被使用。");
        }
        // 2.加密
//        对用户密码进行MD5加密，增加安全性。
//        String encryptPassword = DigestUtils.md5DigestAsHex((SALT + userPassword).getBytes());
        // 使用Bcrypt算法对用户密码加密
        String encryptPassword = passwordEncoder.encode(userPassword);
        // 构造新用户对象，保存到数据库。
        // 3.插入数据
        User user = new User();
        user.setUserAccount(userAccount);
        user.setUserPassword(encryptPassword);
        user.setPlanetCode(planetCode);
        boolean saveResult = this.save(user);
        if (!saveResult) {
            return -1; // 如果保存失败，直接返回错误码。
        }
        // 注册成功，返回新用户的ID。
        return user.getId();
    }

    /**
     * 用户登录方法。
     * 此方法通过用户账号和密码来验证用户身份。如果验证成功，将用户信息存储在会话中，并返回脱敏后的用户信息。
     * @param userAccount 用户账号，需符合特定格式，不少于4位且不能包含特殊字符。
     * @param userPassword 用户密码，不少于8位。
     * @param request HTTP请求对象，用于设置用户登录状态。
     * @return 脱敏后的用户信息，用于客户端显示。如果登录失败（如账号密码不匹配、账号包含特殊字符等），返回null。
     */
    @Override
    public User userLogin(String userAccount, String userPassword, HttpServletRequest request) {
        //1.校验：首先检查账号和密码的格式是否正确，包括长度和特殊字符的检查。
        if (StringUtils.isAnyBlank(userAccount, userPassword)) {
            throw new BusinessException(ErrorCode.PARAMS_ERROR, "账号或密码不能为空");
        }
        if (userAccount.length() < 4) {
            throw new BusinessException(ErrorCode.PARAMS_ERROR, "账号长度不足4位");
        }
        if (userPassword.length() < 8) {
            throw new BusinessException(ErrorCode.PARAMS_ERROR, "密码长度不足8位");
        }
        //账户不能包含特殊字符
        String validPattern = "[`~!@#$%^&*()+=|{}':;',\\\\[\\\\].<>/?~！@#￥%……&*（）——+|{}【】‘；：”“’。，、？]";
        Matcher matcher = Pattern.compile(validPattern).matcher(userAccount);
        if (matcher.find()) {
            throw new BusinessException(ErrorCode.PARAMS_ERROR, "账号包含特殊字符");
        }
//        2.加密：对用户输入的密码进行加密，以匹配数据库中加密后的密码。
//        String encryptPassword = DigestUtils.md5DigestAsHex((SALT + userPassword).getBytes());
        //3.用户验证：通过账号和加密后的密码查询数据库，验证用户是否存在。
        //查询用户是否存在
        QueryWrapper<User> queryWrapper = new QueryWrapper<>();
        queryWrapper.eq("userAccount", userAccount);
//        queryWrapper.eq("userPassword", encryptPassword);
        User user = userMapper.selectOne(queryWrapper);
        //用户不存在或密码不匹配
//        if (user == null) {
        if (user == null || !passwordEncoder.matches(userPassword, user.getUserPassword())) {
            log.info("登录失败，账号或密码错误");
            throw new BusinessException(ErrorCode.NOT_LOGIN, "登录失败，账号或密码错误");
        }
        //4.用户脱敏
        User safetyUser = getSafetyUser(user);
        //5.登录状态记录：如果用户验证成功，将脱敏后的用户信息存储在会话中，以便后续请求能够识别用户身份。
        request.getSession().setAttribute(USER_LOGIN_STATE, safetyUser);
        return safetyUser;
    }

    /**
     * 用户信息脱敏处理。
     * 为了保护用户隐私，对敏感信息进行脱敏处理，只保留部分对外显示的信息。
     * @param originUser 原始用户对象，包含了用户的完整信息。
     * @return 脱敏后的用户对象，只包含对外展示的非敏感信息。如果输入的原始用户对象为null，返回null。
     */
    @Override
    public User getSafetyUser(User originUser) {
        // 判断原始用户对象是否为null，如果是，则直接返回null，表示没有可处理的用户信息。
        if (originUser == null) {
            return null;
        }
        // 创建一个新的用户对象，用于存储脱敏后的信息。
        User safetyUser = new User();
        safetyUser.setId(originUser.getId());
        safetyUser.setUserName(originUser.getUserName());
        safetyUser.setUserAccount(originUser.getUserAccount());
        safetyUser.setAvatarUrl(originUser.getAvatarUrl());
        safetyUser.setGender(originUser.getGender());
        safetyUser.setPhone(originUser.getPhone());
        safetyUser.setEmail(originUser.getEmail());
        safetyUser.setPlanetCode(originUser.getPlanetCode());
        safetyUser.setUserRole(originUser.getUserRole());
        safetyUser.setUserStatus(originUser.getUserStatus());
        safetyUser.setCreateTime(originUser.getCreateTime());
        safetyUser.setTags(originUser.getTags());// 新加入的配置
        return safetyUser;
    }

    /**
     * 根据给定的ID集合查询用户信息。
     * 此方法直接通过ID集合从数据库中检索用户信息，不进行任何脱敏处理。
     * @param ids 用户ID的集合
     * @return 返回匹配给定ID集合的用户列表
     */
    @Override
    public List<User> getUsersByIds(List<Long> ids) {
        // 根据ID集合直接查询用户列表
        List<User> users = listByIds(ids);
        return users;
    }

    /**
     * 分页查询用户信息。
     * 此方法根据给定的页码和每页大小参数进行分页查询。查询结果未进行脱敏处理。
     * @param currentPage 当前页码
     * @param pageSize 每页记录数
     * @return 返回包含当前页用户信息的分页对象
     */
    @Override
    public IPage<User> getUsersByPage(int currentPage, int pageSize) {
        // 创建分页对象，执行分页查询
        Page<User> page = new Page<>(currentPage, pageSize);
        IPage<User> usersPage = this.page(page);
        return usersPage;
    }

    /**
     * 用户注销操作。
     * 从当前会话中移除用户登录状态，实现用户的注销功能。
     * @param request 当前HTTP请求对象，用于获取当前用户会话。
     * @return 返回操作结果，1表示注销成功。
     */
    @Override
    public int userLogout(HttpServletRequest request) {
        // 从当前用户会话中移除用户登录状态的属性，此操作使得用户的登录状态失效。
        request.getSession().removeAttribute(USER_LOGIN_STATE);
        // 返回1，代表用户成功注销。这里返回的数值可以用于后续的逻辑判断或者作为操作成功的标志。
        return 1;
    }

    /**
     * 创建用户操作。
     * 接收一个包含用户创建必需信息的请求体，并在校验通过后创建一个新的用户记录。
     * 校验内容包括账户、密码、用户名和星球编号的非空校验，以及账户和星球编号的唯一性校验。
     * @param userCreateRequest 包含用户创建信息的请求体对象。
     * @return 创建成功返回true，否则抛出相应的业务异常。
     */
    @Override
    public boolean createUser(UserCreateRequest userCreateRequest) {
        // 校验账户、密码、用户名、星球编号不能为空
        if (StringUtils.isAnyBlank(
                userCreateRequest.getUserAccount(),
                userCreateRequest.getUserPassword(),
                userCreateRequest.getUserName(),
                userCreateRequest.getPlanetCode())
        ) {
            throw new BusinessException(ErrorCode.NULL_ERROR, "账户、密码、用户名、星球编号不能为空");
        }
        // 账户是否已存在
        if (checkUserAccountExists(userCreateRequest.getUserAccount())) {
            throw new BusinessException(ErrorCode.PARAMS_ERROR, "账号已存在");
        }
        // 星球编号是否已存在
        if (checkPlanetCodeExists(userCreateRequest.getPlanetCode())) {
            throw new BusinessException(ErrorCode.PARAMS_ERROR, "星球编号已存在");
        }
        // 创建用户对象并设置字段
        User user = new User();
        user.setUserAccount(userCreateRequest.getUserAccount());
        user.setUserName(userCreateRequest.getUserName());
        user.setPlanetCode(userCreateRequest.getPlanetCode());
        user.setAvatarUrl(userCreateRequest.getAvatarUrl());
        user.setGender(userCreateRequest.getGender());
        user.setPhone(userCreateRequest.getPhone());
        user.setEmail(userCreateRequest.getEmail());
        user.setUserRole(userCreateRequest.getUserRole() != null ? userCreateRequest.getUserRole() : UserConstant.DEFAULT_ROLE); // 默认为普通用户，如果未指定角色
        // 密码加密
        String encryptedPassword = DigestUtils.md5DigestAsHex((SALT + userCreateRequest.getUserPassword()).getBytes());
        user.setUserPassword(encryptedPassword);
        return save(user);
    }


    /**
     * 根据提供的用户信息更新用户。
     * 此方法会根据 userUpdateRequest 中提供的信息更新数据库中相应的用户记录。
     * 具体校验逻辑如下：
     * - 用户ID不能为空，确保能正确定位需要更新的用户记录。
     * - 用户名如果提供，其长度不能少于4位。
     * - 账号如果提供，不能包含特殊字符。
     * - 密码如果提供，其长度不能少于8位，并对密码进行MD5加密处理。
     * - 星球编号如果提供，其长度不能超过5位。
     * - 账号和星球编号如果提供，需要检查它们的唯一性，以避免与其他用户的账号或星球编号冲突。
     * @param userUpdateRequest 包含要更新用户信息的请求体。
     * @return 更新成功返回true，否则返回false。
     */
    /*
    @Override
    public boolean updateUser(UserUpdateRequest userUpdateRequest) {
        // 检查更新请求中的用户ID是否为空
        if (userUpdateRequest.getId() == null) {
            throw new BusinessException(ErrorCode.NULL_ERROR, "用户ID不能为空");
        }
        // 若更新请求中包含用户名，则进行长度校验
        if (StringUtils.isNotBlank(userUpdateRequest.getUserName()) && userUpdateRequest.getUserName().length() < 4) {
            throw new BusinessException(ErrorCode.PARAMS_ERROR, "用户名长度过短，至少需要4位。");
        }
        // 账号特殊字符校验
        if (StringUtils.isNotBlank(userUpdateRequest.getUserAccount())) {
            String validPattern = "[`~!@#$%^&*()+=|{}':;',\\[\\].<>/?~！@#￥%……&*（）——+|{}【】‘；：”“’。，、？]";
            Matcher matcher = Pattern.compile(validPattern).matcher(userUpdateRequest.getUserAccount());
            if (matcher.find()) {
                throw new BusinessException(ErrorCode.PARAMS_ERROR, "账号包含非法字符。");
            }
        }
        // 若更新请求中包含用户密码，则进行长度校验
        if (StringUtils.isNotBlank(userUpdateRequest.getUserPassword()) && userUpdateRequest.getUserPassword().length() < 8) {
            throw new BusinessException(ErrorCode.PARAMS_ERROR, "密码长度过短，至少需要8位。");
        }
        // 若更新请求中包含星球编号，则进行长度校验
        if (StringUtils.isNotBlank(userUpdateRequest.getPlanetCode()) && userUpdateRequest.getPlanetCode().length() > 5) {
            throw new BusinessException(ErrorCode.PARAMS_ERROR, "星球编号过长，不得超过5位。");
        }
        // 账户重复性校验
        if (StringUtils.isNotBlank(userUpdateRequest.getUserAccount())) {
            User existingUser = userMapper.selectOne(new QueryWrapper<User>().eq("user_account", userUpdateRequest.getUserAccount()));
            // 如果存在一个不同ID的用户拥有相同的账户，则抛出异常
            if (existingUser != null && !existingUser.getId().equals(userUpdateRequest.getId())) {
                throw new BusinessException(ErrorCode.PARAMS_ERROR, "账户已存在。");
            }
        }
        // 星球编号重复性校验
        if (StringUtils.isNotBlank(userUpdateRequest.getPlanetCode())) {
            User existingUser = userMapper.selectOne(new QueryWrapper<User>().eq("planet_code", userUpdateRequest.getPlanetCode()));
            // 如果存在一个不同ID的用户拥有相同的星球编号，则抛出异常
            if (existingUser != null && !existingUser.getId().equals(userUpdateRequest.getId())) {
                throw new BusinessException(ErrorCode.PARAMS_ERROR, "星球编号已被使用。");
            }
        }
        // 构造更新条件
        UpdateWrapper<User> updateWrapper = new UpdateWrapper<>();
        updateWrapper.eq("id", userUpdateRequest.getId());
        // 准备更新的用户实体
        User user = new User();
        BeanUtils.copyProperties(userUpdateRequest, user);
        // 如果提供了密码，则进行加密处理
        if (StringUtils.isNotBlank(user.getUserPassword())) {
            String encryptedPassword = DigestUtils.md5DigestAsHex((SALT + user.getUserPassword()).getBytes());
            user.setUserPassword(encryptedPassword);
        }
        // 执行更新操作
        int updateCount = userMapper.update(user, updateWrapper);
        return updateCount > 0;
    }
    */

    /**
     * 根据用户ID删除用户。
     * 调用MyBatis Plus的deleteById方法，根据提供的用户ID执行删除操作。
     * 如果删除操作影响的行数大于0，则返回true表示删除成功；否则返回false表示删除失败。
     * @param id 用户的ID。
     * @return 删除成功返回true，失败返回false。
     */
    @Override
    public boolean deleteUserById(Long id) {
        // 这里简单地调用了MyBatis Plus的内置方法进行删除，实际情况可能需要更复杂的逻辑
        int rows = userMapper.deleteById(id);
        return rows > 0;
    }

    /**
     * 实现用户头像更新。
     * @param userId 用户的ID，标识需要更新头像的用户。
     * @param file 上传的头像文件。
     * @return 存储后的头像URL。
     * @throws BusinessException 如果文件为空、保存文件失败或更新用户信息失败，将抛出业务异常。
     */
    @Override
    public String updateUserAvatar(Long userId, MultipartFile file) {
        // 检查文件是否为空
        if (file.isEmpty()) {
            throw new BusinessException(ErrorCode.PARAMS_ERROR, "头像文件不能为空");
        }
        // 生成文件名和存储路径，避免文件名冲突，通常会对文件名进行处理
        String fileName = System.currentTimeMillis() + "_" + file.getOriginalFilename();
        String storagePath = "/path/to/avatar/storage"; // 实际存储路径根据项目情况设置
        Path storageDirectory = Paths.get(storagePath);
        if (!Files.exists(storageDirectory)) {
            try {
                Files.createDirectories(storageDirectory);
            } catch (IOException e) {
                log.error("创建目录失败", e);
                throw new BusinessException(ErrorCode.SYSTEM_ERROR, "创建头像保存目录失败");
            }
        }
        Path filePath = Paths.get(storagePath, fileName);// 将存储路径和文件名组合成完整的文件路径
        // 尝试将文件保存到服务器
        try {
            // 获取上传文件的输入流，把保存路径存储到服务器，如果已经存在，那就替换它
            Files.copy(file.getInputStream(), filePath, StandardCopyOption.REPLACE_EXISTING);
        } catch (IOException e) {
            log.error("头像文件保存失败", e);
            throw new BusinessException(ErrorCode.SYSTEM_ERROR, "头像文件保存失败");
        }
        // 根据实际情况拼接头像URL，得到一个可以访问该文件的URL
        String avatarUrl = "/url/to/avatar/" + fileName;
        // 更新用户记录中的头像URL
        User user = new User();
        user.setId(userId);
        user.setAvatarUrl(avatarUrl);
        // 执行更新操作
        int result = userMapper.updateById(user);
        if (result != 1) {
            throw new BusinessException(ErrorCode.SYSTEM_ERROR, "更新用户头像失败");
        }
        return avatarUrl;
    }

    /**
     * 根据标签搜索用户（入口）
     * @param tagNameList 用户输入的标签列表
     * @return 符合条件的用户列表
     */
    @Override
    public List<User> searchUsersByTags(List<String> tagNameList) {
        if (CollectionUtils.isEmpty(tagNameList)){
            throw new BusinessException(ErrorCode.PARAMS_ERROR);
        }
        // SQL查询
//        return searchUsersByTagsSql(tagNameList);
        // 内存查询
        return searchUsersByTagsInMemory(tagNameList);
    }

    /**
     * 更新用户信息。
     * 该方法用于根据提供的用户对象更新用户数据。
     * 它首先检查用户ID是否有效，然后根据用户的权限（管理员或非管理员）
     * 决定是否可以更新所请求的用户数据。
     * @param user 封装了要更新的用户信息的User对象。
     * @param loginUser 当前登录的User对象，用于进行权限验证。
     * @return 更新操作影响的数据库记录数。通常返回1表示更新成功，返回0表示未进行更新。
     * @throws BusinessException 如果用户ID无效、登录用户无权更新其他用户信息、或指定ID的用户不存在时抛出。
     */
    @Override
    public int updateUser(User user, User loginUser){
        long userId = user.getId();
        if (userId <= 0) {
            throw new BusinessException(ErrorCode.PARAMS_ERROR);
        }
        // TODO 补充校验，如果用户没有传任何要更新的值，就直接报错，不执行 update 语句
        // 如果是管理员，允许更新任意用户
        // 如果不是管理员，只允许更新当前（自己的）用户
        if (!isAdmin(loginUser) && userId != loginUser.getId()) {
            throw new BusinessException(ErrorCode.NO_AUTH);
        }
        User oldUser = userMapper.selectById(userId);
        if (oldUser == null) {
            throw new BusinessException(ErrorCode.NULL_ERROR);
        }
        return userMapper.updateById(user);
    }

    /**
     * 从HTTP请求中获取当前登录的用户。
     * 该方法尝试从提供的HttpServletRequest中获取当前登录用户的信息。
     * 它检查请求的会话中是否存在用户对象，如果不存在则抛出认证异常。
     * @param request 当前HTTP请求对象，用于访问会话信息。
     * @return 如果存在有效的会话并且会话中包含用户信息，则返回对应的User对象。
     * @throws BusinessException 如果请求为null或会话中没有用户信息，抛出没有认证的异常。
     */
    @Override
    public User getLoginUser(HttpServletRequest request) {
        if (request == null) {
            return null;
        }
        Object userObj = request.getSession().getAttribute(USER_LOGIN_STATE);
        if (userObj == null) {
            throw new BusinessException(ErrorCode.NO_AUTH);
        }
        return (User) userObj;
    }

    /**
     * 判断当前操作用户是否为管理员。
     * @param request HTTP请求对象，用于获取当前用户。
     * @return 如果当前用户为管理员，则返回true；否则返回false。
     */
    @Override
    public boolean isAdmin(HttpServletRequest request) {
        // 从会话中获取用户对象
        Object userObj = request.getSession().getAttribute(USER_LOGIN_STATE);
        User user = (User) userObj;
        // 如果用户为空或者用户角色不是管理员，则返回false
        if (user == null || user.getUserRole() != ADMIN_ROLE) {
            return false;
        }
        // 否则，用户是管理员，返回true
        return true;
    }

    /**
     * 判断当前操作用户是否为管理员。
     * @param loginUser 用户对象
     * @return 如果当前用户为管理员，则返回true；否则返回false。
     */
    @Override
    public boolean isAdmin(User loginUser) {
        // 如果用户为空或者用户角色不是管理员，则返回false
        if (loginUser == null || loginUser.getUserRole() != ADMIN_ROLE) {
            return false;
        }
        // 否则，用户是管理员，返回true
        return true;
    }

}

